package com.atguigu.tingshu.search.service.impl;
import co.elastic.clients.elasticsearch.core.search.CompletionSuggestOption;
import co.elastic.clients.elasticsearch.core.search.Suggester;
import co.elastic.clients.elasticsearch.core.search.Suggestion;
import com.atguigu.tingshu.model.search.SuggestIndex;
import com.atguigu.tingshu.vo.search.AlbumInfoIndexVo;


import co.elastic.clients.elasticsearch.ElasticsearchClient;
import co.elastic.clients.elasticsearch._types.FieldValue;
import co.elastic.clients.elasticsearch._types.SortOrder;
import co.elastic.clients.elasticsearch._types.aggregations.Aggregate;
import co.elastic.clients.elasticsearch._types.aggregations.LongTermsAggregate;
import co.elastic.clients.elasticsearch._types.aggregations.LongTermsBucket;
import co.elastic.clients.elasticsearch._types.aggregations.TopHitsAggregate;
import co.elastic.clients.elasticsearch._types.query_dsl.BoolQuery;
import co.elastic.clients.elasticsearch._types.query_dsl.NestedQuery;
import co.elastic.clients.elasticsearch.core.SearchRequest;
import co.elastic.clients.elasticsearch.core.SearchResponse;
import co.elastic.clients.elasticsearch.core.search.Hit;
import co.elastic.clients.json.JsonData;
import com.alibaba.fastjson.JSONObject;
import com.atguigu.tingshu.album.client.AlbumInfoFeignClient;
import com.atguigu.tingshu.common.execption.GuiguException;
import com.atguigu.tingshu.common.result.Result;
import com.atguigu.tingshu.model.album.BaseCategory3;
import com.atguigu.tingshu.model.search.AlbumInfoIndex;
import com.atguigu.tingshu.query.search.AlbumIndexQuery;
import com.atguigu.tingshu.search.service.SearchService;
import com.atguigu.tingshu.vo.search.AlbumSearchResponseVo;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import java.util.*;
import java.util.stream.Collectors;


@Slf4j
@Service
@SuppressWarnings({"unchecked", "rawtypes"})
public class SearchServiceImpl implements SearchService {

    @Autowired
    private AlbumInfoFeignClient albumInfoFeignClient;

    @Autowired
    private ElasticsearchClient elasticsearchClient;


    @SneakyThrows
    @Override
    public List<Map<String, Object>> channel(Long c1Id) {
        //1.根据一级分类查询三级分类下的7个三级分类(置顶)
       Result<List<BaseCategory3>> baseCategory3s = albumInfoFeignClient.findTopCategory3ByC1Id(c1Id);
        List<BaseCategory3> baseCategory3sData = baseCategory3s.getData();
        if (baseCategory3sData == null){
            throw new GuiguException(201, "一级分类不存在三级分类信息");
        }
        //2.获取所有3级分类的id,并且要求是fieldValue类型
        List<FieldValue> c3IdFieldValue = baseCategory3sData.stream().map(baseCategory3 -> FieldValue.of(baseCategory3.getId())).collect(Collectors.toList());
        //3.将三级分类的集合对象转成一个Map对象,key:三级分类的Id  value:三级分类的完整对象
        Map<Long, BaseCategory3> c3IdAndValueMap = baseCategory3sData.stream().collect(Collectors.toMap(BaseCategory3::getId, baseCategory3 -> baseCategory3));

        //4.准备搜索
        // 4.1.拼接dsl语句
        SearchRequest searchRequest = buildChannelDsl(c3IdFieldValue);
        //5.发送搜索请求
        SearchResponse<AlbumInfoIndex> response = elasticsearchClient.search(searchRequest, AlbumInfoIndex.class);
        //6.解析数据给前端
        List<Map<String,Object>> result=parseResult(response,c3IdAndValueMap);
        //7.返回数据
        return result;
    }


    /**
     * 搜索提示
     * @param keyword
     * @return
     */
    @SneakyThrows
    @Override
    public Set<String>  completeSuggest(String keyword) {

        //1.构建一个searchRequestBuilder对象
        SearchRequest.Builder searchRequestBuilder = new SearchRequest.Builder();

        //1.1 构建一个SuggesterBuilder对象
        Suggester.Builder suggesterBuilder = new Suggester.Builder();
        Suggester suggester = suggesterBuilder.
                suggesters("suggestionKeyword", fsb -> fsb
                        .prefix(keyword).completion(csb -> csb.field("keyword").skipDuplicates(true)))
                .suggesters("suggestionKeywordPinyin", fsb -> fsb
                        .prefix(keyword).completion(csb -> csb.field("keywordPinyin").skipDuplicates(true)))
                .suggesters("suggestionKeywordSequence", fsb -> fsb
                        .prefix(keyword).completion(csb -> csb.field("keywordSequence").skipDuplicates(true))).build();
        SearchRequest suggestinfo = searchRequestBuilder.suggest(suggester).index("suggestinfo").build();
        System.out.println("提示词的dsl语句:"+suggestinfo.toString());
        //2.发送请求
        SearchResponse<SuggestIndex> suggestIndexResponse = elasticsearchClient.search(suggestinfo, SuggestIndex.class);
        //3.解析数据
        Set<String> set=parseSuggesterResult(suggestIndexResponse); //标关键字开头的文档
        //不够,找包含关键字的
        SearchResponse<AlbumInfoIndex> albumInfoIndexResponse = elasticsearchClient.search(b -> b
                .index("albuminfo")
                .query(qb -> qb
                        .match(mqb -> mqb
                                .field("albumTitle")
                                .query(keyword))), AlbumInfoIndex.class);
        for (Hit<AlbumInfoIndex> hit : albumInfoIndexResponse.hits().hits()) {
            if (set.size()>=10)
            break;
            String albumTitle = hit.source().getAlbumTitle();
            set.add(albumTitle);
        }

        return set;
    }

    /**
     * 解析提示词的响应结果
     * @param searchResponse
     * @return
     */
    private Set<String> parseSuggesterResult(SearchResponse<SuggestIndex> searchResponse) {
        //suggestionKeyWord :结果  suggestionKeywordPinyin:结果  suggestionKeywordSequence:结果
        HashSet<String> resultSet = new HashSet<>();
        Map<String, List<Suggestion<SuggestIndex>>> suggestMap = searchResponse.suggest();
        for (Map.Entry<String, List<Suggestion<SuggestIndex>>> stringListEntry : suggestMap.entrySet()) {
            List<Suggestion<SuggestIndex>> value = stringListEntry.getValue();
            for (Suggestion<SuggestIndex> suggestIndexSuggestion : value) {
                List<CompletionSuggestOption<SuggestIndex>> options = suggestIndexSuggestion.completion().options();
                for (CompletionSuggestOption<SuggestIndex> option : options) {
                    String title = option.source().getTitle();
                    resultSet.add(title);
                }
            }
        }
        return resultSet;
    }




    @SneakyThrows
    @Override
    public AlbumSearchResponseVo search(AlbumIndexQuery albumIndexQuery) {

        //1.构建带条件的dsl语句
         SearchRequest searchRequest =buildConditionSearchDsl(albumIndexQuery);
        //2.根据dsl让Elasticsearch进行搜索
        SearchResponse<AlbumInfoIndex> response = elasticsearchClient.search(searchRequest, AlbumInfoIndex.class);
        //3.根据Response响应解析数据并且封装返回前端要的数据
        AlbumSearchResponseVo albumSearchResponseVo= parseResponseResult(response);

        albumSearchResponseVo.setPageNo(albumIndexQuery.getPageNo());//满足条件的专辑当前页码
        albumSearchResponseVo.setPageSize(albumIndexQuery.getPageSize());//满足条件的专辑每页显示数量
        Long total = albumSearchResponseVo.getTotal();
        Integer pageSize = albumIndexQuery.getPageSize();
        Long totalPages= total % pageSize == 0 ? total / pageSize : total / pageSize + 1;
        albumSearchResponseVo.setTotalPages(totalPages);//满足条件的专辑总页码
        //4.返回数据
        return albumSearchResponseVo;
    }

    /**
     * 解析响应结果
     * @param response
     * @param
     * @return
     */
    private AlbumSearchResponseVo parseResponseResult(SearchResponse<AlbumInfoIndex> response) {
       //1.创建一个返回对象
        AlbumSearchResponseVo albumSearchResponseVo = new AlbumSearchResponseVo();
        long total = response.hits().total().value();
        albumSearchResponseVo.setTotal(total); //满足条件的专辑数量(总页数)



        ArrayList<AlbumInfoIndexVo> albumInfoIndexVos = new ArrayList<>();
        for (Hit<AlbumInfoIndex> hit : response.hits().hits()) {
            AlbumInfoIndex source = hit.source();
            AlbumInfoIndexVo albumInfoIndexVo = new AlbumInfoIndexVo();
            BeanUtils.copyProperties(source, albumInfoIndexVo);
            List<String> albumTitle = hit.highlight().get("albumTitle");
            if (!CollectionUtils.isEmpty(albumTitle)){
                String albumHighLightTitle = albumTitle.get(0);
                albumInfoIndexVo.setAlbumTitle(albumHighLightTitle); //标题高亮
            }
            albumInfoIndexVos.add(albumInfoIndexVo);
        }
        albumSearchResponseVo.setList(albumInfoIndexVos);//满足条件的专辑对象集合
        return albumSearchResponseVo;
    }

    /**
     * 构建带条件的dsl语句
     * @param albumIndexQuery
     * @return
     */
    private SearchRequest buildConditionSearchDsl(AlbumIndexQuery albumIndexQuery) {
        //1.构建一个SearchRequestBuilder对象 :其实就是dsl语句中最外层的{}
        SearchRequest.Builder searchRequestBuilder = new SearchRequest.Builder();

        //2.构建一个BoolQueryBuilder对象
        BoolQuery.Builder boolQueryBuilder = new BoolQuery.Builder();


        //3.判断前的携带的搜索条件
        //3.1.判断是否携带了关键字
        String keyword = albumIndexQuery.getKeyword();
        if (!StringUtils.isEmpty(keyword)){
            //关键字查询专辑标题
            boolQueryBuilder.should(qb -> qb.match(mq -> mq.field("albumTitle").query(keyword)));
            //关键字查询专辑简介
            boolQueryBuilder.should(qb -> qb.match(mq -> mq.field("albumIntro").query(keyword)));
            //关键字查询主播名
            boolQueryBuilder.should(qb -> qb.match(mq -> mq.field("announcerName").query(keyword)));
        }
        //3.2判断前端是否携带了分类id
        //判断有没有携带一级分类Id
        Long category1Id = albumIndexQuery.getCategory1Id();
        if (category1Id != null){
            boolQueryBuilder.must(qb->qb.term(tqb->tqb.field("category1Id").value(category1Id)));
        }
        //判断有没有携带二级分类Id
        Long category2Id = albumIndexQuery.getCategory2Id();
        if (category2Id != null){
            boolQueryBuilder.must(qb->qb.term(tqb->tqb.field("category2Id").value(category2Id)));
        }
        //判断有没有携带三级分类Id
        Long category3Id = albumIndexQuery.getCategory3Id();
        if (category3Id != null){
            boolQueryBuilder.must(qb->qb.term(tqb->tqb.field("category3Id").value(category3Id)));
        }
        //3.3判断有没有携带标签条件
        List<String> attributeList = albumIndexQuery.getAttributeList();
        if (!CollectionUtils.isEmpty(attributeList)){
            for (String attIdAndValue : attributeList) {
                    String[] split = attIdAndValue.split(":");
                    String attId = split[0]; //标签的属性id
                    String attValue = split[1];  //标签的属性值id

                //创建一个NestedQueryBuilder对象
                NestedQuery.Builder nestedQueryBuilder = new NestedQuery.Builder();
                nestedQueryBuilder.path("attributeValueIndexList")
                        .query(qb->qb
                                .bool(bqb->bqb
                                        .must(mqb->mqb
                                                .term(tqb->tqb.
                                                        field("attributeValueIndexList.attributeId")
                                                        .value(attId)))
                                        .must(mqb->mqb
                                                .term(tqb->tqb
                                                        .field("attributeValueIndexList.valueId")
                                                        .value(attValue)))));

                NestedQuery nestedQuery = nestedQueryBuilder.build(); //得到一个NestedQuery对象
                boolQueryBuilder.must(qb->qb.nested(nestedQuery)); //NestedQuery对象通过must方法添加到BoolQueryBuilder对象中
            }
        }

        BoolQuery boolQuery = boolQueryBuilder.build();//得到BoolQuery对象
        searchRequestBuilder.query(boolQuery._toQuery());//将BoolQuery对象变为query对象放到SearchRequestBuilder对象中去

        //------------------------------------基础条件结束----------------------------------------
        //4.分页
        Integer pageNo = albumIndexQuery.getPageNo();
        Integer pageSize = albumIndexQuery.getPageSize();
        searchRequestBuilder.from((pageNo-1)*pageSize).size(pageSize);
        //5.高亮
        searchRequestBuilder.highlight(hb->hb.fields("albumTitle",hfb->hfb.preTags("<font style='color:red'>").postTags("</font>")));
        //6.排序
        // 综合排序[1:desc] 播放量[2:desc] 发布时间[3:desc];asc:升序 desc:降序  dimension:维度  order:排序规则  0:asc  1:desc
        String order = albumIndexQuery.getOrder();
        if (!StringUtils.isEmpty(order)){
            String[] split = order.split(":");
            String dimension = split[0];
            String rule = split[1];
             SortOrder sortRule=rule=="desc"?SortOrder.Desc:SortOrder.Asc;
            switch (dimension){
                case "1": //综合排序
                    searchRequestBuilder.sort(sob->sob.field(fsb->fsb.field("hotScore").order(sortRule)));
                    break;
                case "2": //播放量排序
                    searchRequestBuilder.sort(sob->sob.field(fsb->fsb.field("playStatNum").order(sortRule)));
                    break;
                case "3": //发布时间排序
                    searchRequestBuilder.sort(sob->sob.field(fsb->fsb.field("createTime").order(sortRule)));
            }
        }else {
            searchRequestBuilder.sort(sob->sob.field(fsb->fsb.field("hotScore").order(SortOrder.Desc))); //处理没有排序条件的
        }
        SearchRequest searchRequest = searchRequestBuilder.index("albuminfo").build();
        System.out.println("条件查询的dsl语句:"+searchRequest.toString());
        return searchRequest;
    }

    /**
     * 解析数据
     * 前端要是是一个Map 这个Map中给和只要返回两个key
     * 7个map对应的这个集合
     * 每个map都要有 这个三级分类下的热度值最高的6个专辑
     * @param response
     * @param c3IdAndValueMap
     * @return
     */
    private List<Map<String, Object>> parseResult(SearchResponse<AlbumInfoIndex> response, Map<Long, BaseCategory3> c3IdAndValueMap) {

        //1.创建一个返回对象
        ArrayList<Map<String, Object>> arrayList = new ArrayList<>();

        //2.解析数据
        //2.1 获取响应中的聚合
        Aggregate category3IdAgg = response.aggregations().get("category3IdAgg");
        //2.2.要将获取到的Aggregate类型转换为聚合字段的精确类型
        LongTermsAggregate lterms = category3IdAgg.lterms();//获取到当时聚合字段的精确类型
        List<LongTermsBucket> buckets = lterms.buckets().array();

        //2.3遍历桶集合
        for (LongTermsBucket bucket : buckets) {
            Long cartegory3Id = bucket.key();//三级分类的id
            HashMap<String, Object> map = new HashMap<>();
            //2.4 获取子聚合
            Aggregate hotScoreAgg = bucket.aggregations().get("hotScoreAgg");

            //2.5 获取子聚合的精确类型
            TopHitsAggregate topHitsAggregate = hotScoreAgg.topHits();
            List<Hit<JsonData>> hits = topHitsAggregate.hits().hits();
            //2.6 定义一个热度值高的6个专辑的集合
            ArrayList<AlbumInfoIndex> albumInfoIndices = new ArrayList<>();
            for (Hit<JsonData> hit : hits) {
                JsonData source = hit.source(); //专辑类型
                AlbumInfoIndex albumInfoIndex = JSONObject.parseObject(source.toString(), AlbumInfoIndex.class);
                albumInfoIndices.add(albumInfoIndex);
            }
            //获取到某个三级分类下热度值最高的6个专辑
            map.put("list",albumInfoIndices); //某个三级分类下热度值最高的6个专辑
            map.put("baseCategory3",c3IdAndValueMap.get(cartegory3Id)); //某个三级分类对应的对象
            arrayList.add(map);
        }


        //返回数据给前端
        return arrayList;
    }

    /**
     * 拼接dsl语句
     * @param c3IdFieldValue
     * @return
     */
    private SearchRequest buildChannelDsl(List<FieldValue> c3IdFieldValue) {
        //1.构建一个SearchRequest对象
        SearchRequest searchRequest = SearchRequest.of(b -> b
                        .index("albuminfo")
                        .query(qb -> qb
                                .terms(tqb -> tqb
                                        .field("category3Id")
                                        .terms(tqfb -> tqfb
                                                .value(c3IdFieldValue))))
                        .aggregations("category3IdAgg",
                                ab->ab
                                        .terms(tab->tab
                                                .field("category3Id")
                                                .size(c3IdFieldValue.size()))
                                        .aggregations("hotScoreAgg",
                                                subAb->subAb
                                                        .topHits(thab->thab
                                                                .sort(sob->sob
                                                                        .field(fsb->fsb
                                                                                .field("hotScore")
                                                                                .order(SortOrder.Desc)))
                                                                .size(6)))
                        )
                );
        System.out.println("dsl语句:"+searchRequest.toString());
        return searchRequest;
    }
}
